/*************************Copyright (c)*************************
 

**--------------------------File Info---------------------------

** File name:
** Last modified Date:

** Last Version:

** Descriptions:
**---------------------------------------------------------------

** Created by:		刘家辉 
** Created date:	2021/01/08

** Version:			V1.0
** Descriptions:	这里是参照《UNIX环境高级编程-第三版》中的编程实例（图1-10 从标准输入中读命令并执行）
** Notes:           该程序的最主要限制是不能向所执行的命令传递参数。例如不能指定要列出目录项的目录名，只能对工作目录执行ls命令。
为了传递参数，需要分析输入行，然后用某种约定把参数分开（可能使用空格或制表符），再将分隔后的各个参数传递给execlp函数。
尽管如此，此程序仍可用来说明UNIX系统的进程控制功能。        
** Tips:            该程序使用了一个不同的提示符（%），以区别于shell的提示符        
**---------------------------------------------------------------
*/

#include "apue.h"
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

static void sig_int(int signal);   //our signal-catching function

int main(void)
{
    char buf[MAXLINE];//from apue.h
    pid_t pid;
    int status;
    /*  
    为了捕捉到此信号，程序需要调用signal函数，其中指定了当产生SIGINT信号时要调用的函数的名字。
    函数名为sig_int,当其被调用时，只是打印一条消息，然后打印一个新的提示符。
    */
    if (signal(SIGINT, sig_int) == SIG_ERR)
        err_sys("signal error");

    printf("%% ");//print prompt (prntf requires %% to print %)

    /* 
    用标准I/O函数fgets从标准输入一次读取一行，
    当键入文件结束符（通常是Crtl+D）作为行的第一个字符时，
    fgets返回一个null指针，于是循环停止，进程也就终止。
    */
    while (fgets(buf, MAXLINE, stdin) != NULL)
    {   
        /* 
        因为fgets返回的每一行都以换行符终止，后随一个null字节，
        因此用标准C函数strlen计算此字符串的长度，然后用一个null字节来替换换行符。
        这样做是因为execlp函数要求的参数是以null结束的而不是以换行符结束的。
        */
        if (buf[strlen(buf) - 1] == '\n')
        {
            buf[strlen(buf) - 1] = 0;//replace newline with null
            
            /* 
            调用fork函数创建一个新进程。新进程是调用进程的一个副本，我们称调用进程为父进程，新创建的进程为子进程。
            fork对父进程返回新的子进程的进程ID（一个非负整数），对于子进程则返回0。
            因为fork创建一个新进程，所以说它被调用一次（由父进程），但返回两次（分别在父进程中和子进程中）。
            */
            if ((pid = fork()) < 0)
            {
                err_sys("fork error");
            }
            else if (pid == 0)//child
            {
                /*  
                在子进程中，调用execlp以执行从标准输入读入的命令。这就用新的程序文件替换了子进程原先执行的程序文件。
                fork和跟随其后的exec两者的组合就是某些操作系统所称的产生（spawn）一个新进程                
                在UNIX系统中，这两个部分分离成两个独立的函数。
                */
                execlp(buf, buf, (char*)0);
                err_ret("couldn't execute: %s",buf);
                exit(127);
            }
            
            /*   
            子进程调用execlp执行新程序文件，而父进程希望等待子进程终止，这是通过调用waitpid来实现的，其参数指定要等待的进程（即pid参数是子进程id）。
            waitpid函数返回子进程的终止状态（status变量）。
            在该程序中，没有使用该值，如果需要，可以用该值准确地判定子进程是如何终止的。
            */
            if ((pid = waitpid(pid, &status, 0)) < 0)//parent
            {
                err_sys("waitpid error");
            }

            printf("%% ");
        }
    }
    exit(0);
}


void sig_int(int signal)
{
    printf("interrupt<sig_int>\n%% ");
}